/************************************************************************
 * AIDE/Tools - Automata Identification Engine
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     
 * Description  :     
 * Change log   :     	-Date:  Change
 ************************************************************************/
using System;
using SMLib.Automata.MM;
using AIDE.OnlineAlgo.NFMs;
using AIDE.Oracles.NFMs;
using AIDE.Filters.NFMs;
using AIDE.Base;
using AIDE.Learning.IA_Learning;
using SMLib.Automata.IA;
using AIDE.Filters.IA;

namespace LearnIt
{
	/// <summary>
	/// Main class for LearnIt command line utility
	/// </summary>
	public class MainClass
	{
		#region -=[ Parameters ]=-
		class Options
		{
			public Options ()
			{
				Help = false;
				Port = -1;
				Model = "";
				kindOfSystemSpecified = false;
				No_Params = false;
			}

			public string cls;
			public bool No_Params;
			public bool Help;
			public bool kindOfSystemSpecified;
			public bool learnFromRemote;
			public int Port;
			public bool Port_Specified;
			public string Model;
			public bool learnIA;
		}

		private static Options ParseParams (string[] args)
		{
			Options opt = new Options ();
			if (args.Length == 0) {
				opt.No_Params = true;
			} else {
				opt.No_Params = false;
				opt.kindOfSystemSpecified = false;
				opt.Port_Specified = false;
				for (int i=0; i<args.Length; i++) {
					Console.WriteLine ("Processing " + args [i]);

					switch (args [i]) {
					case "-help":
						opt.Help = true;
						break;
					case "-IA":
						opt.learnIA = true;
						break;
					case "-port":
						if (i >= args.Length)
							throw new Exception ("Exception: After -port parameter, the port is expected");
						i++;
						string portSt = args [i];
						int port;
						Console.WriteLine ("port configured as  " + args [i]);

						port = Int32.Parse (portSt); 
						opt.Port = port;
						opt.Port_Specified = true;

						if (opt.kindOfSystemSpecified)
							throw new Exception ("Exception: The kind of learning cannot be specified more than once!");
						opt.kindOfSystemSpecified = true;
						opt.learnFromRemote = true;
						break;
					
					case "-l":
						if (opt.kindOfSystemSpecified)
							throw new Exception ("Exception: The kind of learning cannot be specified more than once!");
						opt.kindOfSystemSpecified = true;
						opt.learnFromRemote = false;
						if (i >= args.Length)
							throw new Exception ("Exception: After -l parameter, the model and class are expected");
						i++;
						opt.Model = args [i];
						if (i >= args.Length)
							throw new Exception ("Exception: After -l parameter, the model and class are expected");
						i++;
						opt.cls = args [i];
						break;
					}					
				}				
			}
			return opt;
		}
		#endregion


		/// <summary>
		/// Learns the I.
		/// </summary>
		/// <param name="ia_sul">he system under learning</param>
		static void LearnIA (IBlackBox_IA ia_sul)
		{
			LearningLog.SetOutput ("./SimulationResult.txt");
			LearningLog.InitLogger ();
			Filter_IA_Cached cache = new Filter_IA_Cached (ia_sul);
			IA_Learner learner = new IA_Learner (cache);
			InterfaceAutomaton ai2 = learner.Learn ();
			Console.Write ("Learning is finished! Size of model:" + ai2.Size);
			Console.Write ("\nExporting the learnt model...");
			SMLib.Interoperability.DOT.DotHelper_IA.WriteToDot (
				LearnItSetting.ConfOf ("LearnIt.ExportTo") + "LearnItResult.dot", ai2);
			Console.Write ("Done!\n");

			AIDE.Base.LearningLog.LogString ("Cache hit ration=" + ((double)cache.NumberOfCacheHit) / cache.TotalNumberOfQueries);
			AIDE.Base.LearningLog.LogString ("Cache requests=" + cache.TotalNumberOfQueries);

			AIDE.Base.LearningLog.PrintLog ();

		}

		/// <summary>
		/// Learns the Mealy machine
		/// </summary>
		/// <param name="sul">the system under learning</param>
		static void LearnMealy (IBlackBox_MM sul)
		{
			LearningLog.SetOutput (AIDE.Base.LibSetting.WorkingPath + "SimulationResult.txt");
			LearningLog.InitLogger ();
			Oracle_MAT_Realistic oracledfm;
			if (LearnItSetting.UseCaching) {
				Console.WriteLine ("Using cache mechanism");
				Filter_MM_Cached sulMMCached = new Filter_MM_Cached (sul);
				oracledfm = new Oracle_MAT_Realistic (sulMMCached);
			} else {
				Console.WriteLine ("Not using cache mechanism");
				oracledfm = new Oracle_MAT_Realistic (sul);
			}
			MealyMachine m2 = null;
			
			if (LearnItSetting.ConfOf ("LearnIt.Algo4MM") == "LPlusM") {
				L_Plus_M learner = new L_Plus_M (oracledfm);
				m2 = learner.Learn ();
			} else if (LearnItSetting.ConfOf ("LearnIt.Algo4MM") == "LOne") {
				L_One learner = new L_One (oracledfm);
				m2 = learner.Learn ();
			} else if (LearnItSetting.ConfOf ("LearnIt.Algo4MM") == "DHC") {
				DHC learner = new DHC (oracledfm);
				m2 = learner.Learn ();
			} else {
				Console.WriteLine ("Error: Unknown learning algorithm!");
			}
			
			if (m2 != null) {
				Console.WriteLine ("Learning was successful");
			
				if (LearnItSetting.ConfOf ("LearnIt.ExportTo") != "") {
					Console.WriteLine ("Exporting the learnt model...");
					SMLib.Interoperability.DOT.DotHelper_MM.WriteToDot (
						LearnItSetting.ConfOf ("LearnIt.ExportTo") + "LearnItResult.dot", m2);
				}
			} else {
				Console.WriteLine ("Failed!");
			}
		}

		/// <summary>
		/// Prints the help message
		/// </summary>
		static void PrintHelp()
		{
			Console.WriteLine ("usage: LearnIt [-help] [-port PORT] [-l library_path name_of_class] [-IA]");
			Console.WriteLine (" -help: shows this message.");
			Console.WriteLine (" -port Port: list to the specified port (1000 is the default).");
			Console.WriteLine (" -IA learns the IA (by deafault, it learns MM");
			Console.WriteLine (" -l library_path name_of_class:" +
			                   "load class from a library [where the class implements IBlackBox_MM].");
		}

		/// <summary>
		/// The entry point of the program, where the program control starts and ends.
		/// </summary>
		/// <param name="args">The command-line arguments.</param>
		public static void Main (string[] args)
		{
			args=new string[] {"-IA"};

			Console.WriteLine ("LearnIt utility: learn a machine from remote");
			Options opt = ParseParams (args);
			if (opt.No_Params) {
				Console.WriteLine ("usage: LearnIt [-help] [-port PORT] [-l library_path name_of_class]");
				return;
			}
			if (opt.Help) {
				PrintHelp ();
				return;
			}

			if (!opt.kindOfSystemSpecified) {
				opt.kindOfSystemSpecified = true;
				opt.learnFromRemote = true;
				Console.WriteLine ("Learn from remote/default port 1000");
				opt.Port = 1000;
			}

			//* set port
			int port = 1000;
			if (opt.learnFromRemote) {
				port = (opt.Port_Specified ? opt.Port : 1000);
				Console.Write ("port = " + port);
			}

			//* initialize config
			Console.Write ("Read configuration...");
			LearnItSetting.Init ();

			//*** learn Mealy machine
			if (!opt.learnIA) {
				Console.WriteLine ("Learning Mealy machines...");
				IBlackBox_MM sul;
				//* create the SUL
				if (opt.learnFromRemote) {
					sul = new SMLib.SUL_MM_WrapperProxy (port);
				} else {
					LoadSUL loader = new LoadSUL (opt.Model);
					sul = loader.LoadMealy (opt.cls);
				}
				//* learn
				LearnMealy (sul);

				if (opt.learnFromRemote) {
					((SMLib.SUL_MM_WrapperProxy)sul).Close ();
				}
			} else {
				Console.WriteLine ("Learning IAs...");
				//**** learn IA
				AIDE.Base.LibSetting.Init ();
				LearningLog.SetOutput (AIDE.Base.LibSetting.WorkingPath + "SimulationResult.txt");
				LearningLog.InitLogger ();
				IBlackBox_IA ia_sul;
				//* create the SUL
				if (opt.learnFromRemote) {
					ia_sul = new SMLib.SUL_IA_WrapperProxy (port);
				} else {
					LoadSUL loader = new LoadSUL (opt.Model);
					ia_sul = loader.LoadIA (opt.cls);
				}
				//* learn
				LearnIA (ia_sul);

				if (opt.learnFromRemote) {
					((SMLib.SUL_IA_WrapperProxy)ia_sul).Close();
				}
			}
			LearningLog.FinishLogging ();
		}
	}
}
